﻿using System;
using System.Text.RegularExpressions;
using FluentParser.Expressions;
using FluentParser.Expressions.Rules.Composition;
using FluentParser.Expressions.Rules.Decoration;
using FluentParser.Expressions.Terminals.Parsing;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace FluentParser.Tests.Expressions.Rules.Composition
{
    [TestClass]
    public class OperatorsExpressionRuleTests
    {
        [TestMethod]
        public void Parse_WhenParsingSingleString_ShouldReturnTheString()
        {
            var operators = CreateStringOperators()
                 .Register("CONCAT", "+", (x, y) => x + y)
                 .Group(GroupingOrientation.LeftToRight, "CONCAT");
            ParsingResult<string> result = operators.Parse("'text' ");

            string expected = "text";
            string actual = result.Value;

            Assert.AreEqual(expected, actual);
            result.AssertLength("'text'");
        }

        [TestMethod]
        public void Parse_WhenParsingTheConcatenationOfTwoStrings_ShouldReturnTheConcatenatedStrings()
        {
            var operators = CreateStringOperators()
                .Register("CONCAT", "+", (x, y) => x + y)
                .Group(GroupingOrientation.LeftToRight, "CONCAT");
            ParsingResult<string> result = operators.Parse("'left' + 'right' ");

            string expected = "leftright";
            string actual = result.Value;

            Assert.AreEqual(expected, actual);
            result.AssertLength("'left' + 'right'");
        }

        [TestMethod]
        public void Parse_WhenParsingASingleNumber_ShouldReturnTheNumber()
        {
            var operators = CreateNumericOperators()
                .Register("ADD", "+", (x, y) => x + y)
                .Group(GroupingOrientation.LeftToRight, "ADD");
            ParsingResult<int> result = operators.Parse("4");

            int expected = 4;
            int actual = result.Value;

            Assert.AreEqual(expected, actual);
            result.AssertLength("4");
        }

        [TestMethod]
        public void Parse_WhenParsingAnAddition_ShouldReturnTheAddedNumbers()
        {
            var operators = CreateNumericOperators()
                .Register("ADD", "+", (x, y) => x + y)
                .Group(GroupingOrientation.LeftToRight, "ADD");
            ParsingResult<int> result = operators.Parse("2 + 4");

            int expected = 2 + 4;
            int actual = result.Value;

            Assert.AreEqual(expected, actual);
            result.AssertLength("2 + 4");
        }

        [TestMethod]
        public void Parse_WhenParsingTwoSubstractionsUsingLeftToRightOrientation_FirstSubstractionShouldBeProcessedFirst()
        {
            var operators = CreateNumericOperators()
                .Register("SUB", "-", (x, y) => x - y)
                .Group(GroupingOrientation.LeftToRight, "SUB");
            ParsingResult<int> result = operators.Parse("2 - 4 - 3 ");

            int expected = (2 - 4) - 3;
            int actual = result.Value;

            Assert.AreEqual(expected, actual);
            result.AssertLength("2 - 4 - 3");
        }

        [TestMethod]
        public void Parse_WhenParsingTwoSubstractionsUsingRightToLeftOrientation_LastSubstractionShouldBeProcessedFirst()
        {
            var operators = CreateNumericOperators()
                .Register("SUB", "-", (x, y) => x - y)
                .Group(GroupingOrientation.RightToLeft, "SUB");
            ParsingResult<int> result = operators.Parse("2 - 4 - 3 ");

            int expected = 2 - (4 - 3);
            int actual = result.Value;

            Assert.AreEqual(expected, actual);
            result.AssertLength("2 - 4 - 3");
        }

        [TestMethod]
        public void Parse_WhenParsingTwoDifferentOperatorsOfTheSamePriorityUsingRightToLeftOrientation_LastOperatorShouldBeAppliedFirst()
        {
            var operators = CreateNumericOperators()
                .Register("SUB", "-", (x, y) => x - y)
                .Register("ADD", "+", (x, y) => x + y)
                .Group(GroupingOrientation.RightToLeft, "SUB", "ADD");
            ParsingResult<int> result = operators.Parse("2 - 4 + 3 ");

            int expected = 2 - (4 + 3);
            int actual = result.Value;

            Assert.AreEqual(expected, actual);
            result.AssertLength("2 - 4 + 3");
        }

        [TestMethod]
        public void Parse_WhenParsingASubstractionAndAMultiplication_MultiplicationShouldBeAppliedFirst()
        {
            var operators = CreateNumericOperators()
                .Register("SUB", "-", (x, y) => x - y)
                .Register("MULT", "*", (x, y) => x*y)
                .Group(GroupingOrientation.LeftToRight, "MULT") // Higher priority
                .Group(GroupingOrientation.LeftToRight, "SUB"); // Lower priority
            ParsingResult<int> result = operators.Parse("2 - 4 * 3 ");

            int expected = 2 - 4 * 3;
            int actual = result.Value;

            Assert.AreEqual(expected, actual);
            result.AssertLength("2 - 4 * 3");
        }

        [TestMethod]
        public void Parse_WhenUsingParenthesisToPriotizeSubstraction_ParenthesisShouldBeAppliedFirst()
        {
            var operators = CreateNumericOperatorsWithParenthesis()
                .Register("SUB", "-", (x, y) => x - y)
                .Register("MULT", "*", (x, y) => x * y)
                .Group(GroupingOrientation.LeftToRight, "MULT") // Higher priority
                .Group(GroupingOrientation.LeftToRight, "SUB"); // Lower priority
            ParsingResult<int> result = operators.Parse("(2 - 4) * 3 ");
            int expected = (2 - 4) * 3;
            int actual = result.Value;

            Assert.AreEqual(expected, actual);
            result.AssertLength("(2 - 4) * 3");
        }

        [TestMethod]
        public void ParseNode_WhenParsingSingleString_ShouldReturnTheString()
        {
            var operators = CreateStringOperators()
                 .Register("ADD", "+", (x, y) => x + y)
                 .Group(GroupingOrientation.LeftToRight, "ADD");
            ParsingNodeResult node = operators.ParseNode("'text' abc");

            string expected = "text";
            string actual = node.Value as string;

            Assert.IsTrue(node.IsSuccess);
            Assert.AreEqual(expected, actual);
            node.AssertLength("'text'");
            node.AssertExploredLength("'text' ?");

            node.AssertChildren(
                true, // String literal
                true, // spaces
                false); // "+"
        }

        [TestMethod]
        public void ParseNode_WhenParsingTheConcatenationOfTwoStrings_ShouldReturnTheConcatenatedStrings()
        {
            var operators = CreateStringOperators()
                .Register("CONCAT", "+", (x, y) => x + y)
                .Group(GroupingOrientation.LeftToRight, "CONCAT");
            ParsingNodeResult node = operators.ParseNode("'left' + 'right' abc");

            string expected = "leftright";
            string actual = node.Value as string;

            Assert.IsTrue(node.IsSuccess);
            Assert.AreEqual(expected, actual);
            node.AssertLength("'left' + 'right'");
            node.AssertExploredLength("'left' + 'right' ?");

            // Children tests
            node.AssertChildren(
                true, // String Literal
                true, // spaces
                true, // "+"
                true, // spaces
                true, // String Literal
                true, // spaces
                false); // "+"
        }

        [TestMethod]
        public void ParseNode_WhenParsingASingleNumber_ShouldReturnTheNumber()
        {
            var operators = CreateNumericOperators()
                .Register("ADD", "+", (x, y) => x + y)
                .Group(GroupingOrientation.LeftToRight, "ADD");
            ParsingNodeResult node = operators.ParseNode("4 ");

            int expected = 4;
            int actual = (int)node.Value;

            Assert.IsTrue(node.IsSuccess);
            Assert.AreEqual(expected, actual);
            node.AssertLength("4");
            node.AssertExploredLength("4 ?");

            node.AssertChildren(
                true, // number
                true, // spaces
                false); // "+"
        }

        [TestMethod]
        public void ParseNode_WhenParsingAnAddition_ShouldReturnTheAddedNumbers()
        {
            var operators = CreateNumericOperators()
                .Register("ADD", "+", (x, y) => x + y)
                .Group(GroupingOrientation.LeftToRight, "ADD");
            ParsingNodeResult node = operators.ParseNode("2 + 4 abc");

            int expected = 2 + 4;
            int actual = (int)node.Value;

            Assert.IsTrue(node.IsSuccess);
            Assert.AreEqual(expected, actual);
            node.AssertLength("2 + 4");
            node.AssertExploredLength("2 + 4 ?");

            node.AssertChildren(
                true, // number
                true, // spaces
                true, // "+"
                true, // spaces
                true, // number
                true, // spaces
                false); // "+"
        }

        [TestMethod]
        public void ParseNode_WhenParsingTwoSubstractionsUsingLeftToRightOrientation_FirstSubstractionShouldBeProcessedFirst()
        {
            var operators = CreateNumericOperators()
                .Register("SUB", "-", (x, y) => x - y)
                .Group(GroupingOrientation.LeftToRight, "SUB");
            ParsingNodeResult node = operators.ParseNode("2 - 4 - 3 abc");

            int expected = (2 - 4) - 3;
            int actual = (int) node.Value;

            Assert.IsTrue(node.IsSuccess);
            Assert.AreEqual(expected, actual);
            node.AssertLength("2 - 4 - 3");
            node.AssertExploredLength("2 - 4 - 3 ?");

            node.AssertChildren(
                true, // number 
                true, // spaces
                true, // "-"
                true, // spaces
                true, // number
                true, // spaces
                true, // "-"
                true, // spaces
                true, // number
                true, // spaces
                false); // "-"
        }

        [TestMethod]
        public void ParseNode_WhenParsingTwoSubstractionsUsingRightToLeftOrientation_LastSubstractionShouldBeProcessedFirst()
        {
            var operators = CreateNumericOperators()
                .Register("SUB", "-", (x, y) => x - y)
                .Group(GroupingOrientation.RightToLeft, "SUB");
            ParsingNodeResult node = operators.ParseNode("2 - 4 - 3 abc");

            int expected = 2 - (4 - 3);
            int actual = (int)node.Value;

            Assert.IsTrue(node.IsSuccess);
            Assert.AreEqual(expected, actual);
            node.AssertLength("2 - 4 - 3");
            node.AssertExploredLength("2 - 4 - 3 ?");

            node.AssertChildren(
                true, // number
                true, // spaces
                true, // "-"
                true, // spaces
                true, // number
                true, // spaces
                true, // "-"
                true, // spaces
                true, // number
                true, // spaces
                false); // "-"
        }

        [TestMethod]
        public void ParseNode_WhenParsingTwoDifferentOperatorsOfTheSamePriorityUsingRightToLeftOrientation_LastOperatorShouldBeAppliedFirst()
        {
            var operators = CreateNumericOperators()
                .Register("SUB", "-", (x, y) => x - y)
                .Register("ADD", "+", (x, y) => x + y)
                .Group(GroupingOrientation.RightToLeft, "SUB", "ADD");
            ParsingNodeResult node = operators.ParseNode("2 - 4 + 3 - abc"); 

            int expected = 2 - (4 + 3);
            int actual = (int) node.Value;

            Assert.IsTrue(node.IsSuccess);
            Assert.AreEqual(expected, actual);
            node.AssertLength("2 - 4 + 3");
            node.AssertExploredLength("2 - 4 + 3 - ?");

            node.AssertChildren(
                true, // number
                true, // spaces
                true, // "-"
                true, // spaces
                true, // number
                true, // spaces
                false, // "-"
                true, // "+"
                true, // spaces
                true, // number
                true, // spaces
                true, // "-"
                true, // spaces
                false, // number
                false); // "+" (backtracking)
        }

        [TestMethod]
        public void ParseNode_WhenParsingASubstractionAndAMultiplication_MultiplicationShouldBeAppliedFirst()
        {
            var operators = CreateNumericOperators()
                .Register("SUB", "-", (x, y) => x - y)
                .Register("MULT", "*", (x, y) => x * y)
                .Group(GroupingOrientation.LeftToRight, "MULT") // Higher priority
                .Group(GroupingOrientation.LeftToRight, "SUB"); // Lower priority
            ParsingNodeResult node = operators.ParseNode("2 -4 * 3 ");

            int expected = 2 - 4 * 3;
            int actual = (int) node.Value;

            Assert.IsTrue(node.IsSuccess);
            Assert.AreEqual(expected, actual);
            node.AssertLength("2 -4 * 3");
            node.AssertExploredLength("2 -4 * 3 ?");

            node.AssertChildren(
                 true, // number
                 true, // spaces
                 true, // "-"
                 true, // spaces
                 true, // number
                 true, // spaces
                 false, // "-"
                 true, // "*"
                 true, // spaces
                 true, // number
                 true, // spaces
                 false, // "-"
                 false); // "*"
        }

        [TestMethod]
        public void ParseNode_WhenUsingParenthesisToPriotizeSubstraction_ParenthesisShouldBeAppliedFirst()
        {
            var operators = CreateNumericOperatorsWithParenthesis()
                .Register("SUB", "-", (x, y) => x - y)
                .Register("MULT", "*", (x, y) => x * y)
                .Group(GroupingOrientation.LeftToRight, "MULT") // Higher priority
                .Group(GroupingOrientation.LeftToRight, "SUB"); // Lower priority
            ParsingNodeResult node = operators.ParseNode("(2 - 4) * 3 ");

            int expected = (2 - 4) * 3;
            int actual = (int) node.Value;

            Assert.IsTrue(node.IsSuccess);
            Assert.AreEqual(expected, actual);
            node.AssertLength("(2 - 4) * 3");
            node.AssertExploredLength("(2 - 4) * 3 ?");

            node.AssertChildren(
                 true, // Sub-expression
                 true, // spaces
                 false, // "-"
                 true, // "*"
                 true, // spaces
                 true, // Sub-expression
                 true, // spaces
                 false, // "-"
                 false); // "*"
        }

        private static OperatorsExpressionRule<string> CreateStringOperators()
        {
            var parseString = new ParseRegexExpressionTerminal<string>(new Regex("\\G[^']*"), x => x.ToString()).Surround("'");
            var spaces = new ParseRegexSymbolTerminal(new Regex("\\G[ ]*"));
            return new OperatorsExpressionRule<string>(parseString, spaces);
        }

        private static OperatorsExpressionRule<int> CreateNumericOperators()
        {
            var parseInteger = new ParseRegexExpressionTerminal<int>(new Regex("\\G-?[0-9]+"), x => Int32.Parse(x.ToString()));
            var spaces = new ParseRegexSymbolTerminal(new Regex("\\G[ ]*"));
            return new OperatorsExpressionRule<int>(parseInteger, spaces);
        }

        private static OperatorsExpressionRule<int> CreateNumericOperatorsWithParenthesis()
        {
            var element = new FutureRule<int>();
            var parseInteger = new ParseRegexExpressionTerminal<int>("-?[0-9]+", x => Int32.Parse(x.ToString()));
            var spaces = new ParseRegexSymbolTerminal("[ ]*");
            var operators = new OperatorsExpressionRule<int>(element, spaces);
            element.Source = parseInteger | operators.Surround("(", ")");

            return operators;
        }

    }
}